<?php
namespace Qy;

/**
* 企业微信公用类
* @author Jamers
* @since 2017.1.1
* @see http://qydev.weixin.qq.com/wiki/index.php?title=%E4%B8%BB%E5%8A%A8%E8%B0%83%E7%94%A8
* @since 包括主动调用及回调模式所有功能（加解密）
*/
class Common {
    public $corpid, $corpsecret;
    public $token, $encodingAesKey;
    private $accesstoken;
    
    private $wxcpt;         //WXBizMsgCrypt类  消息加解密类

    public function __construct($ary = array('corpid'=>'', 'corpsecret'=>'', 'token'=>'', 'aeskey'=>'', 'accesstoken'=>'')) {
        if ($ary) {
            if (property_exists(__CLASS__,'corpid')) if (isset($ary['corpid']) && $ary['corpid']) $this->corpid = $ary['corpid'];
            if (property_exists(__CLASS__,'corpsecret')) if (isset($ary['corpsecret']) && $ary['corpsecret']) $this->corpsecret = $ary['corpsecret'];
            if (property_exists(__CLASS__,'token')) if (isset($ary['token']) && $ary['token']) $this->token = $ary['token'];
            if (property_exists(__CLASS__,'encodingAesKey')) if (isset($ary['aeskey']) && $ary['aeskey']) $this->encodingAesKey = $ary['aeskey'];
            if (property_exists(__CLASS__,'accesstoken')) if (isset($ary['accesstoken']) && $ary['accesstoken']) $this->accesstoken = $ary['accesstoken'];
        }
    }
    
    /**
    * 初始化Token/AESKey参数
    * 
    * @param mixed $corpid
    * @param mixed $token
    * @param mixed $encodingAesKey
    */
    public function init_token($corpid='',$token='',$encodingAesKey='') {
        if ($corpid) $this->corpid = $corpid;
        if ($token) $this->token = $token;
        if ($encodingAesKey) $this->encodingAesKey = $encodingAesKey;        
    }
    
    /**
    * 从类中取access token，如果没有再从缓存中取
    * 
    */
    public function read_access_token() {
        if (empty($this->access_token)) {
            $this->access_token = $this->getAccessToken();
        }
        return $this->access_token;
    }

    /**
    * 保存日志数据
    * 
    * @param mixed $data
    * @param mixed $file
    * @param mixed $recreate
    */
    public function SaveLogs($data,$file='',$recreate=false) {
        if ($file=='') $file = 'qywx_log_'.date('Ymd').'.txt';
        if ($recreate) {
            $fp = fopen(ZW_ROOT."/{$file}",'w');
        }else{
            $fp = fopen(ZW_ROOT."/{$file}",'a');
        }
        $str = date("Y-m-d H:i:s")."\t".$data."\n";
        fwrite($fp,$str);
        fclose($fp);
    }
    
    /**
    * 数组转换成XML文本
    * 
    * @param mixed $arr
    * @param mixed $level
    */
    public function array2xml($arr, $level = 1) {
        $s = $level == 1 ? "<xml>" : '';
        foreach ($arr as $tagname => $value) {
            if (is_numeric($tagname)) {
                $tagname = $value['TagName'];
                unset($value['TagName']);
            }
            if (!is_array($value)) {
                $s .= "<{$tagname}>" . (!is_numeric($value) ? '<![CDATA[' : '') . $value . (!is_numeric($value) ? ']]>' : '') . "</{$tagname}>";
            } else {
                $s .= "<{$tagname}>" . $this->array2xml($value, $level + 1) . "</{$tagname}>";
            }
        }
        $s = preg_replace("/([\x01-\x08\x0b-\x0c\x0e-\x1f])+/", ' ', $s);
        return $level == 1 ? $s . "</xml>" : $s;
    }

    /**
    * 检查XML字符串是否有前缀
    * 
    * @param mixed $string
    * @param mixed $class_name
    * @param mixed $options
    * @param mixed $ns
    * @param mixed $is_prefix
    * @return SimpleXMLElement
    */
    private function isimplexml_load_string($string, $class_name = 'SimpleXMLElement', $options = 0, $ns = '', $is_prefix = false) {
        libxml_disable_entity_loader(true);
        if (preg_match('/(\<\!DOCTYPE|\<\!ENTITY)/i', $string)) {
            return false;
        }
        return simplexml_load_string($string, $class_name, $options, $ns, $is_prefix);
    }
    /**
    * XML解析成数组
    * 
    * @param mixed $xml
    */
    public function xml2array($xml) {
        if (empty($xml)) {
            return array();
        }
        $result = array();
        $xmlobj = $this->isimplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        if($xmlobj instanceof \SimpleXMLElement) {
            $result = json_decode(json_encode($xmlobj), true);
            if (is_array($result)) {
                return $result;
            } else {
                return array();
            }
        } else {
            return $result;
        }
    }
    /**
    * 取AccessToken，会缓存
    * 
    * @param mixed $corpid
    * @param mixed $corpsecret
    * @param boolean $force 是否强制重新生成
    * @return AccessToken
    */
    public function getAccessToken($corpid='',$corpsecret='',$force = false) {
        if ($corpid) $this->corpid = $corpid;
        if ($corpsecret) $this->corpsecret = $corpsecret;

        $result = $this->getLocalAccessToken($this->corpid, $this->corpsecret);
        if (empty($result)||$force) {
            $result = $this->getRemoteAccessToken($this->corpid, $this->corpsecret);
            if (empty($result)) {
                return FALSE;
            }else {
                return $result;
            }
        }else {
            return $result;
        }
    }

    /**
     * 获取存储在本地的AccessToken
     * @param $corpid 企业ID
     * @param $corpsecret 管理组的凭证密钥
     */
    private function getLocalAccessToken($corpid = FALSE, $corpsecret = FALSE) {
        if (empty($corpid) || empty($corpsecret)) {
            return FALSE;
        }

        if (!file_exists(dirname(__FILE__) . '/token.bin')) {
            return FALSE;
        }

        $result = file_get_contents(dirname(__FILE__) . '/token.bin');
        if (empty($result)) {
            return FALSE;
        }

        $result = json_decode($result, TRUE);
        $key = md5($corpid . $corpsecret);
        if (isset($result[$key])) {
            if (time() - 7200 > $result[$key][1]) {
                // token已超时
                return FALSE;
            } else {
                // token未超时
                return $result[$key][0];
            }
        } else {
            return FALSE;
        }
    }
    
    /**
     * 从服务器上获取AccessToken
     * @param $corpid 企业ID
     * @param $corpsecret 管理组的凭证密钥
     */
    private function getRemoteAccessToken($corpid = FALSE, $corpsecret = FALSE) {
        if (empty($corpid) || empty($corpsecret)) {
            return FALSE;
        }
        
        $url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken";
        $data = array('corpid'=>$corpid, 'corpsecret'=>$corpsecret);
        
        $result = $this->httpRequest($url, $data);
        if (isset($result['access_token'])) {
            $this->saveAccessToken($corpid, $corpsecret, $result['access_token']);
            return $result['access_token'];
        }else {
            return FALSE;
        }
    }
    /**
    * HTTP请求
    * 
    * @param mixed $url
    * @param mixed $parameters
    * @param mixed $method
    */
    public function httpRequest($url, $parameters = NULL, $method = 'get') {
        $method = strtolower($method);
        switch ($method) {
            case 'get' :
                return $this -> httpGetRequest($url, $parameters);
                break;
            case 'post' :
                return $this -> httpPostRequest($url, $parameters);
                break;
            default :
                return FALSE;
                break;
        }
    }
    
    /**
    * 发起CURL请求，比httpRequest多些功能
    * 
    * @param mixed $url
    * @param mixed $post
    * @param mixed $cookie
    * @param mixed $returnCookie
    */
    public function curlRequest($url, $post = '', $cookie = '', $returnCookie = 0) {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)');
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1);
        //curl_setopt($curl, CURLOPT_REFERER, "http://XXX");
        if ($post) {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post));
        }
        if ($cookie) {
            curl_setopt($curl, CURLOPT_COOKIE, $cookie);
        }
        curl_setopt($curl, CURLOPT_HEADER, $returnCookie);
        curl_setopt($curl, CURLOPT_TIMEOUT, 10);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        $data = curl_exec($curl);
        if (curl_errno($curl)) {
            return curl_error($curl);
        }
        curl_close($curl);
        if ($returnCookie) {
            list($header, $body) = explode("\r\n\r\n", $data, 2);
            preg_match_all("/Set\-Cookie:([^;]*);/", $header, $matches);
            $info['cookie'] = substr($matches[1][0], 1);
            $info['content'] = $body;
            return $info;
        } else {
            return $data;
        }
    }
    
    /**
    * 通过POST发送文件
    * 
    * @param mixed $url
    * @param mixed $data
    */
    public function uploadFileByPost($url, $data) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SAFE_UPLOAD, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_BINARYTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $result = curl_exec($ch);
        if ($error = curl_error($ch)) {
            die($error);
        }
        curl_close($ch);

        return json_decode($result, TRUE);
    }
    /**
    * 解析XML字符串成数组，支持将键值改为小写
    * 
    * @param mixed $xml
    * @param mixed $keytolower
    */
    public function parsexml($xml,$keytolower = false) {
        $ret = $this->xml2array($xml);
        if ($keytolower) {
            $ret = $this->tolower($ret);
        }
        return $ret;
    }
    
    /**
    * 遍历将数组键值改为小写
    * 
    * @param mixed $ary
    */
    private function tolower($ary) {
        if (!is_array($ary)) return $ary;
        $ret = array();
        foreach ($ary as $k => $v) {
            if (is_array($v)) {
                $ret[strtolower($k)] = $this->tolower($v);
            }else{
                $ret[strtolower($k)] = $v;
            }
        }
        return $ret;
    }
    /**
     * 发起httpGET请求
     * @param $url 请求的URL
     * @param $parameters 请求的参数，以数组形式传递
     */
    private function httpGetRequest($url, $parameters = NULL) {
        if (empty($url)) {
            return FALSE;
        }
        // 将请求参数追加在url后面
        if (!empty($parameters) && is_array($parameters) && count($parameters)) {
            $is_first = TRUE;
            foreach ($parameters as $key => $value) {
                if ($is_first) {
                    $url .= "?" . $key . "=" . urlencode($value);
                    $is_first = FALSE;
                } else {
                    $url .= "&" . $key . "=" . urlencode($value);
                }
            }
        }

        //初始化CURL
        $ch = curl_init();
        // 设置要请求的URL
        curl_setopt($ch, CURLOPT_URL, $url);
        // 设置不显示头部信息
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        // 将curl_exec()获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // 设置本地不检测SSL证书
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        // 执行请求动作，并获取结果
        $result = curl_exec($ch);
        if ($error = curl_error($ch)) {
            die($error);
        }
        // 关闭CURL
        curl_close($ch);

        return json_decode($result, TRUE);
    }

    /**
     * 发起httpPOST请求
     * @param $url 请求的URL
     * @param $parameters 请求的参数，以数组形式传递
     */
    private function httpPostRequest($url, $parameters = array()) {
        if (empty($url)) {
            return FALSE;
        }
        $headers = array(
            //'Content-Type' => 'application/x-www-form-urlencoded',
            'Content-Type' => 'application/json',
        );
        // 初始化CURL
        $ch = curl_init();
        // 设置要请求的URL
        curl_setopt($ch, CURLOPT_URL, $url);
        // 设置不显示头部信息
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        // 设置不将请求结果直接输出在标准输出里，而是返回
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // 设置本地不检测SSL证书
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置post方式提交
        curl_setopt($ch, CURLOPT_POST, TRUE);
        // 设置请求参数
        if (!empty($parameters)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->json_encode_ex($parameters));
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        // 执行请求动作，并获取结果
        $result = curl_exec($ch);
        if ($error = curl_error($ch)) {
            die($error);
        }
        // 关闭CURL
        curl_close($ch);

        return json_decode($result, TRUE);
    }
    public function saveAccessToken($corpid, $corpsecret, $token) {
        if (empty($corpid) || empty($corpsecret) || empty($token)) {
            return FALSE;
        }
        if (!file_exists(dirname(__FILE__) . '/token.bin')) {
            file_put_contents(dirname(__FILE__) . '/token.bin', "");
        }

        $result = file_get_contents(dirname(__FILE__) . '/token.bin');

        $result = json_decode($result, TRUE);
        $key = md5($corpid . $corpsecret);
        if ($result) {
            //清除过期AccessToken
            foreach ($result as $k => $v) {
                if (time() - 7200 > $v[1]) unset($result[$k]);
            }
        }
        $result[$key] = array($token, time());

        if (file_put_contents(dirname(__FILE__) . '/token.bin', json_encode($result))) {
            return TRUE;
        } else {
            return FALSE;
        }
    }

    /**
     * 对内容进行json编码，并且保持汉字不会被编码
     * @param $value 被编码的对象
     * @return 编码结果字符串
     */
    public function json_encode_ex($value) {
        if (version_compare(PHP_VERSION, '5.4.0', '<')) {
            $str = json_encode($value);
            $str = preg_replace_callback("#\\\u([0-9a-f]{4})#i", function($matchs) {
                return iconv('UCS-2BE', 'UTF-8', pack('H4', $matchs[1]));
            }, $str);
            return $str;
        } else {
            return json_encode($value, JSON_UNESCAPED_UNICODE);
        }
    }
    
    /**
    * 检查是否已创建wxcpt加解密对象
    * 
    */
    private function check_wxcpt() {
        if (!is_object($this->wxcpt)) {
            if ($this->token && $this->corpid && $this->encodingAesKey) {
                $this->wxcpt = new \Qy\WXBizMsgCrypt($this->token,$this->encodingAesKey,$this->corpid);
            }else{
                throw new Exception('参数不全，无法创建wxcpt对象！');
            }
        }
    }

    
    /**
    * Echostr解密
    * 
    * @param mixed $sMsgSignature
    * @param mixed $sTimeStamp
    * @param mixed $sNonce
    * @param mixed $sEchoStr
    * @param mixed $sReplyEchoStr
    */
    public function echostrDecrypt($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr) {
        $this->check_wxcpt();
        $this->wxcpt = new WXBizMsgCrypt($this->token,$this->encodingAesKey,$this->corpid);
        $sReplyEchoStr = "";
        $errCode = $this->wxcpt->VerifyURL($sMsgSignature, $sTimeStamp, $sNonce, $sEchoStr, $sReplyEchoStr);
        if ($errCode == 0) {
            return $sReplyEchoStr;
        }else{
            return "";
        }
    }
    
    /**
    * 回调数据中解密函数
    * 
    * @param mixed $sMsgSignature
    * @param mixed $sTimeStamp
    * @param mixed $sNonce
    * @param mixed $sPostData
    */
    public function DecryptMsg($sMsgSignature, $sTimeStamp = null, $sNonce, $sPostData) {
        $this->check_wxcpt();
        $ret = '';
        $errCode = $this->wxcpt->DecryptMsg($sMsgSignature, $sTimeStamp, $sNonce, $sPostData, $ret);
        if ($errCode == 0) {
            $xml = new DOMDocument();
            $xml->loadXML($ret);
            $content = $xml->getElementsByTagName('Content')->item(0)->nodeValue;
            return $content;
        }else{
            return "";
        }
    }
    
    /**
    * 加密回复消息
    * 
    * @param mixed $sReplyMsg
    * @param mixed $sTimeStamp
    * @param mixed $sNonce
    * @return mixed
    */
    public function EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce) {
        $this->check_wxcpt();
        $ret = '';
        $errCode = $this->wxcpt->EncryptMsg($sReplyMsg, $sTimeStamp, $sNonce, $ret);
        if ($errCode == 0) {
            return $ret;
        }else{
            return "";
        }
    }
    
    /**
    * 获取微信服务器的ip段
    * 
    * @param mixed $access_token
    */
    public function getCallbackip($access_token = '') {
        if (empty($access_token)) $access_token = $this->read_access_token();
        $url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip?access_token={$access_token}";
        $ret = $this->httpRequest($url);
        return $ret;
    }
    
}
?>
